package timing.ukulele.oss;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.Protocol;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.*;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import timing.ukulele.oss.config.OSSProperties;
import timing.ukulele.oss.exceptions.UkuleleOSSException;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 文件存储 使用AmazonS3服务器
 *
 * @author 11697
 */
@Slf4j
public class UkuleleS3Client implements UkuleleOSSTemplate {
    /**
     * AmazonS3客户端
     */
    private AmazonS3 amazonS3;

    private String url;
    /**
     * 桶名称
     */
    private String bucketName;

    /**
     * 私有桶名称
     */
    private String privateBucketName;

    /**
     * 允许的文件后缀 白名单
     */
    private String fileTypeWhiteList;

    @Override
    public String getFileTypeWhiteList() {
        return fileTypeWhiteList;
    }

    @Override
    public void close() {
        if (amazonS3 != null) {
            amazonS3.shutdown();
            log.info("关闭oss客户端");
        }
    }

    public UkuleleS3Client(OSSProperties ossProperties) {
        this.fileTypeWhiteList = ossProperties.getFileTypeWhiteList();
        this.url = ossProperties.getUrl();
        String accessKey = ossProperties.getAccessKey();
        String secretKey = ossProperties.getSecretKey();
        this.bucketName = ossProperties.getBucketName();
        this.privateBucketName = ossProperties.getPrivateBucketName();
        try {
            AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
            ClientConfiguration clientConfig = new ClientConfiguration();
            clientConfig.setProtocol(Protocol.HTTP);
            clientConfig.setSignerOverride("S3SignerType");

            AmazonS3ClientBuilder builder = AmazonS3ClientBuilder.standard()
                    .withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
                    .withClientConfiguration(clientConfig)
                    .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(url, "cn-north-1"));
            this.amazonS3 = builder.build();
            log.info("s3客户端初始化成功");
        } catch (Exception e) {
            log.error("s3客户端初始化失败：{}", e.getMessage(), e);
        }
    }

    @Override
    public String uploadFileByInputStream(MultipartFile file, String fileObjectName, String contentType, Boolean isPrivate) throws UkuleleOSSException, IOException {
        //判断文件后缀名是否在白名单中，如果不在报异常，中止文件保存
        checkFileSuffixName(file);
        return uploadFileByInputStream(file.getInputStream(), fileObjectName, contentType, isPrivate);
    }

    @Override
    public String uploadFileByInputStream(MultipartFile file, String fileObjectName, Boolean isPrivate) throws UkuleleOSSException, IOException {
        String contentType = "plain/text";
        return uploadFileByInputStream(file, fileObjectName, contentType, isPrivate);
    }

    @Override
    public String uploadFileByInputStream(InputStream inputStream, String fileObjectName, String contentType, Boolean isPrivate) throws UkuleleOSSException {
        try {
            String bucket = checkBucket(isPrivate);
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(contentType);
            metadata.addUserMetadata("title", "ukuleleTitle");
            PutObjectResult putObjectResult = this.amazonS3.putObject(bucket, fileObjectName, inputStream, metadata);
            log.info("save file to S3 store success:{}", putObjectResult.getContentMd5());
            return url + fileObjectName;
        } catch (Exception e) {
            log.error("save file to S3 store error:", e);
            throw new UkuleleOSSException("S3 文件保存失败：" + e.getMessage(), e);
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (Exception e) {
                log.error("close InputStream error:", e);
            }
        }
    }

    @Override
    public String preSignedGetObject(String objectName, Integer expires) {
        String url = "";
        if (expires == null) {
            expires = 604800;
        }
        LocalDateTime expireTime = LocalDateTime.now().plus(expires, ChronoUnit.SECONDS);
        Instant instant = expireTime.atZone(ZoneId.systemDefault()).toInstant();
        GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(this.privateBucketName, objectName);
        generatePresignedUrlRequest.setExpiration(Date.from(instant));
        URL generatePresignedUrl = amazonS3.generatePresignedUrl(generatePresignedUrlRequest);
        if (generatePresignedUrl != null)
            return generatePresignedUrl.toString();
        return url;
    }

    @Override
    public String pubGetObject(String objectName) {
        String url = "";
        GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(this.bucketName, objectName);
        URL generatePresignedUrl = amazonS3.generatePresignedUrl(generatePresignedUrlRequest);
        if (generatePresignedUrl != null)
            return generatePresignedUrl.toString();
        return url;
    }

    @Override
    public String copyFile(String sourcePath, Boolean sourcePrivate, String targetPath, Boolean targetPrivate) throws UkuleleOSSException {
        String sourceBucket = checkBucket(sourcePrivate);
        String targetBucket = checkBucket(targetPrivate);
        this.amazonS3.copyObject(sourcePath, sourceBucket, targetPath, targetBucket);
        return targetPath;
    }

    @Override
    public byte[] downloadFile(String fileObjectName, Boolean isPrivate) throws UkuleleOSSException {
        byte[] fileBytes = null;
        InputStream inputStream = null;
        try {
            String bucket = checkBucket(isPrivate);
            S3Object object = this.amazonS3.getObject(new GetObjectRequest(bucket, fileObjectName));
            inputStream = object.getObjectContent();
            if (inputStream == null) {
                log.error("file {} not exist in S3 store ", fileObjectName);
                throw new UkuleleOSSException("file not exist in S3 store, objectName=" + fileObjectName);
            }
            fileBytes = IOUtils.toByteArray(inputStream);
        } catch (Exception e) {
            log.error("read file from minio store error:", e);
            throw new UkuleleOSSException("read file from S3 store error, objectName=" + fileObjectName);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Exception e) {

                }
            }
        }
        return fileBytes;
    }

    @Override
    public void downloadFile(String fileObjectName, Boolean isPrivate, HttpServletResponse response) throws IOException {
        byte[] bytes = downloadFile(fileObjectName, isPrivate);
        ServletOutputStream outputStream = response.getOutputStream();
        outputStream.write(bytes);
    }

    @Override
    public void deleteFile(String fileObjectName, Boolean isPrivate) {
        String bucket = "";
        try {
            bucket = checkBucket(isPrivate);
            this.amazonS3.deleteObject(bucket, fileObjectName);
        } catch (Exception e) {
            log.warn("delete file in S3 store fail, bucket={}, file={}", bucket, fileObjectName);
        }
    }

    @Override
    public void deleteFiles(List<String> fileObjectNames, Boolean isPrivate) {
        String bucket = "";
        try {
            if (CollectionUtils.isEmpty(fileObjectNames)) {
                return;
            }
            bucket = checkBucket(isPrivate);
            List<DeleteObjectsRequest.KeyVersion> keys = fileObjectNames.stream().map(fileObject -> new DeleteObjectsRequest.KeyVersion(fileObject)).collect(Collectors.toList());
            DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucket).withKeys(keys);
            this.amazonS3.deleteObjects(deleteObjectsRequest);
        } catch (Exception e) {
            log.warn("删除文件仓库失败, bucket={}, file={}", bucket, fileObjectNames.toString());
        }
    }

    private String checkBucket(Boolean isPrivate) {
        if (isPrivate == null) {
            throw new UkuleleOSSException("请设置相关桶是否为私用");
        }
        if (isPrivate) {
            if (!StringUtils.hasText(this.privateBucketName)) {
                throw new UkuleleOSSException("请设置私有桶名称");
            }
            return privateBucketName;
        } else {
            if (!StringUtils.hasText(this.bucketName)) {
                throw new UkuleleOSSException("请设置公有桶名称");
            }
            return bucketName;
        }
    }
}
